<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>《悲惨世界》人物关系图</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
        }
        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #e4e8ed 100%);
            color: #333;
            min-height: 100vh;
            padding: 20px;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        header {
            text-align: center;
            margin-bottom: 30px;
            padding: 25px;
            background: white;
            border-radius: 12px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
            border: 1px solid #eaeaea;
        }
        h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
            color: #2c3e50;
            text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
        }
        .subtitle {
            font-size: 1.2rem;
            color: #5a6c7d;
            max-width: 800px;
            margin: 0 auto;
            line-height: 1.6;
        }
        .chart-container {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }
        .chart-wrapper {
            background: white;
            border-radius: 12px;
            padding: 20px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
            border: 1px solid #eaeaea;
        }
        #main {
            width: 100%;
            height: 600px;
        }
        .controls {
            display: flex;
            justify-content: center;
            gap: 15px;
            margin-top: 20px;
            flex-wrap: wrap;
        }
        button {
            padding: 10px 20px;
            background: #3498db;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            transition: all 0.3s ease;
            font-weight: bold;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
        }
        button:hover {
            background: #2980b9;
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
        }
        .info-panel {
            background: white;
            padding: 25px;
            border-radius: 12px;
            margin-top: 20px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
            border: 1px solid #eaeaea;
        }
        .info-panel h2 {
            color: #2c3e50;
            margin-bottom: 15px;
            border-bottom: 2px solid #3498db;
            padding-bottom: 10px;
        }
        .info-content {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
        }
        .info-box {
            background: #f8f9fa;
            padding: 18px;
            border-radius: 8px;
            border-left: 4px solid #3498db;
        }
        .info-box h3 {
            color: #2c3e50;
            margin-bottom: 10px;
        }
        .info-box p {
            line-height: 1.6;
            color: #5a6c7d;
        }
        .legend {
            display: flex;
            justify-content: center;
            flex-wrap: wrap;
            gap: 15px;
            margin-top: 15px;
        }
        .legend-item {
            display: flex;
            align-items: center;
            gap: 8px;
            background: #f8f9fa;
            padding: 8px 12px;
            border-radius: 20px;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
        }
        .legend-color {
            width: 15px;
            height: 15px;
            border-radius: 50%;
        }
        footer {
            text-align: center;
            margin-top: 30px;
            padding: 20px;
            color: #7f8c8d;
            font-size: 0.9rem;
        }
        @media (max-width: 768px) {
            h1 {
                font-size: 2rem;
            }
            #main {
                height: 450px;
            }
            .controls {
                flex-direction: column;
                align-items: center;
            }
            button {
                width: 80%;
            }
        }
    </style>
</head>
<body>
<div class="container">
    <header>
        <h1>《悲惨世界》人物关系图</h1>
        <p class="subtitle">维克多·雨果的文学巨著《悲惨世界》中复杂的人物关系网络可视化展示。图中节点代表小说中的角色，连线表示角色之间的关系。</p>
    </header>

    <div class="chart-container">
        <div class="chart-wrapper">
            <div id="main"></div>
            <div class="controls">
                <button id="reset">重置视图</button>
                <button id="focus-central">聚焦核心人物</button>
                <button id="toggle-labels">切换标签显示</button>
                <button id="change-layout">切换布局</button>
            </div>
        </div>

        <div class="info-panel">
            <h2>关于此关系图</h2>
            <div class="info-content">
                <div class="info-box">
                    <h3>数据说明</h3>
                    <p>此关系图基于《悲惨世界》小说中的人物关系数据构建。节点大小表示角色在小说中的重要性，连线表示角色之间的关联。</p>
                </div>
                <div class="info-box">
                    <h3>交互操作</h3>
                    <p>• 拖拽：移动视图位置<br>• 滚轮：缩放视图<br>• 点击节点：高亮显示相关连接<br>• 点击图例：筛选显示特定类别角色</p>
                </div>
                <div class="info-box">
                    <h3>图例说明</h3>
                    <div class="legend">
                        <div class="legend-item">
                            <div class="legend-color" style="background-color: #e74c3c;"></div>
                            <span>主要角色</span>
                        </div>
                        <div class="legend-item">
                            <div class="legend-color" style="background-color: #2c3e50;"></div>
                            <span>次要角色</span>
                        </div>
                        <div class="legend-item">
                            <div class="legend-color" style="background-color: #3498db;"></div>
                            <span>反派角色</span>
                        </div>
                        <div class="legend-item">
                            <div class="legend-color" style="background-color: #f39c12;"></div>
                            <span>其他角色</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <footer>
        <p>数据来源: Apache ECharts 示例数据集 | 可视化实现: ECharts 5.4.3</p>
    </footer>
</div>

<script>
    // 初始化图表
    var chartDom = document.getElementById('main');
    var myChart = echarts.init(chartDom);

    // 模拟数据 - 在实际应用中，这里应该从服务器获取JSON数据
    // 为了演示，我们创建一个简化的《悲惨世界》人物关系数据
    function generateMockData() {
        var categories = [
            {name: '主要角色'},
            {name: '次要角色'},
            {name: '反派角色'},
            {name: '其他角色'}
        ];

        var nodes = [
            {id: '0', name: '冉阿让', category: 0, symbolSize: 50},
            {id: '1', name: '沙威', category: 2, symbolSize: 40},
            {id: '2', name: '芳汀', category: 0, symbolSize: 40},
            {id: '3', name: '珂赛特', category: 0, symbolSize: 35},
            {id: '4', name: '马吕斯', category: 0, symbolSize: 35},
            {id: '5', name: '德纳第', category: 2, symbolSize: 30},
            {id: '6', name: '爱潘妮', category: 1, symbolSize: 25},
            {id: '7', name: '伽弗洛什', category: 1, symbolSize: 25},
            {id: '8', name: '米里哀主教', category: 1, symbolSize: 30},
            {id: '9', name: '马伯夫', category: 3, symbolSize: 20},
            {id: '10', name: '安灼拉', category: 1, symbolSize: 25},
            {id: '11', name: '公白飞', category: 3, symbolSize: 20},
            {id: '12', name: '弗以伊', category: 3, symbolSize: 20},
            {id: '13', name: '古费拉克', category: 3, symbolSize: 20},
            {id: '14', name: '巴阿雷', category: 3, symbolSize: 20}
        ];

        var links = [
            {source: '0', target: '1'},
            {source: '0', target: '2'},
            {source: '0', target: '3'},
            {source: '0', target: '8'},
            {source: '2', target: '3'},
            {source: '3', target: '4'},
            {source: '4', target: '5'},
            {source: '4', target: '6'},
            {source: '4', target: '10'},
            {source: '5', target: '6'},
            {source: '5', target: '7'},
            {source: '6', target: '7'},
            {source: '7', target: '10'},
            {source: '10', target: '11'},
            {source: '10', target: '12'},
            {source: '10', target: '13'},
            {source: '10', target: '14'},
            {source: '11', target: '12'},
            {source: '11', target: '13'},
            {source: '11', target: '14'},
            {source: '1', target: '5'}
        ];

        return {nodes: nodes, links: links, categories: categories};
    }

    // 生成模拟数据
    var graph = generateMockData();

    // 配置选项
    var option = {
        title: {
            text: '《悲惨世界》人物关系图',
            subtext: '人物关系网络',
            top: 'top',
            left: 'center',
            textStyle: {
                color: '#2c3e50',
                fontSize: 20
            },
            subtextStyle: {
                color: '#5a6c7d',
                fontSize: 14
            }
        },
        tooltip: {
            formatter: function(params) {
                if (params.dataType === 'node') {
                    return params.data.name + (params.data.category !== undefined ?
                        '<br/>类别: ' + graph.categories[params.data.category].name : '');
                }
                return params.data.source + ' → ' + params.data.target;
            },
            backgroundColor: 'rgba(255, 255, 255, 0.9)',
            borderColor: '#eaeaea',
            textStyle: {
                color: '#333'
            }
        },
        legend: {
            data: graph.categories.map(function(a) {
                return a.name;
            }),
            top: 40,
            textStyle: {
                color: '#333'
            },
            selectedMode: 'multiple'
        },
        animationDuration: 1500,
        animationEasingUpdate: 'quinticInOut',
        series: [{
            name: 'Les Miserables',
            type: 'graph',
            layout: 'force',
            data: graph.nodes,
            links: graph.links,
            categories: graph.categories,
            roam: true,
            focusNodeAdjacency: true,
            label: {
                show: true,
                position: 'right',
                formatter: '{b}',
                color: '#333'
            },
            lineStyle: {
                color: 'source',
                curveness: 0.3,
                width: 1.5
            },
            emphasis: {
                focus: 'adjacency',
                lineStyle: {
                    width: 3
                }
            },
            force: {
                repulsion: 200,
                gravity: 0.1,
                edgeLength: 100
            }
        }],
        backgroundColor: 'rgba(255, 255, 255, 0)'
    };

    // 应用配置
    myChart.setOption(option);

    // 添加交互功能
    document.getElementById('reset').addEventListener('click', function() {
        myChart.dispatchAction({
            type: 'graphRoam',
            zoom: 1,
            originX: 0,
            originY: 0
        });
    });

    document.getElementById('focus-central').addEventListener('click', function() {
        myChart.dispatchAction({
            type: 'focusNodeAdjacency',
            seriesIndex: 0,
            dataIndex: 0  // 聚焦冉阿让
        });
    });

    var labelsVisible = true;
    document.getElementById('toggle-labels').addEventListener('click', function() {
        labelsVisible = !labelsVisible;
        option.series[0].label.show = labelsVisible;
        myChart.setOption(option);
    });

    var currentLayout = 'force';
    document.getElementById('change-layout').addEventListener('click', function() {
        currentLayout = currentLayout === 'force' ? 'circular' : 'force';
        option.series[0].layout = currentLayout;
        myChart.setOption(option);
    });

    // 响应窗口大小变化
    window.addEventListener('resize', function() {
        myChart.resize();
    });
</script>
</body>
</html>